<!DOCTYPE html>
<html lang="en">

<!-- Head tag -->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="keyword"  content="">
    <link rel="shortcut icon" href="/img/favicon.ico">

    <title>
        
          流媒体加密 - 浩麟的博客
        
    </title>

    <link rel="canonical" href="https://wuhaolin.cn/2017/08/01/流媒体加密/">

    <!-- Bootstrap Core CSS -->
    
<link rel="stylesheet" href="/css/bootstrap.min.css">


    <!-- Custom CSS -->
    
<link rel="stylesheet" href="/css/hux-blog.min.css">


    <!-- Pygments Highlight CSS -->
    
<link rel="stylesheet" href="/css/highlight.css">


    <!-- Custom Fonts -->
    <!-- <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
    <!-- Hux change font-awesome CDN to qiniu -->
    <link href="https://cdn.staticfile.org/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">


    <!-- Hux Delete, sad but pending in China
    <link href='http://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
    <link href='http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/
    css'>
    -->


    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

    <!-- ga & ba script hoook -->
    <script></script>
<meta name="generator" content="Hexo 4.1.1"></head>


<!-- hack iOS CSS :active style -->
<body ontouchstart="">

<!-- Navigation -->
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header page-scroll">
            <button type="button" class="navbar-toggle">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">浩麟的博客</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <!-- Known Issue, found by Hux:
            <nav>'s height woule be hold on by its content.
            so, when navbar scale out, the <nav> will cover tags.
            also mask any touch event of tags, unfortunately.
        -->
        <div id="huxblog_navbar">
            <div class="navbar-collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li>
                        <a href="/">Home</a>
                    </li>
                    <li>
                        <a href="https://github.com/gwuhaolin/blog/" target="_blank">订阅</a>
                    </li>

                    

                        
                    

                        
                        <li>
                            <a href="/about/">关于</a>
                        </li>
                        
                    

                        
                        <li>
                            <a href="/archives/">归档</a>
                        </li>
                        
                    

                        
                        <li>
                            <a href="/tags/">标签</a>
                        </li>
                        
                    

                </ul>
            </div>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container -->
</nav>
<script>
    // Drop Bootstarp low-performance Navbar
    // Use customize navbar with high-quality material design animation
    // in high-perf jank-free CSS3 implementation
    var $body   = document.body;
    var $toggle = document.querySelector('.navbar-toggle');
    var $navbar = document.querySelector('#huxblog_navbar');
    var $collapse = document.querySelector('.navbar-collapse');

    $toggle.addEventListener('click', handleMagic)
    function handleMagic(e){
        if ($navbar.className.indexOf('in') > 0) {
        // CLOSE
            $navbar.className = " ";
            // wait until animation end.
            setTimeout(function(){
                // prevent frequently toggle
                if($navbar.className.indexOf('in') < 0) {
                    $collapse.style.height = "0px"
                }
            },400)
        }else{
        // OPEN
            $collapse.style.height = "auto"
            $navbar.className += " in";
        }
    }
</script>


<!-- Main Content -->

<!-- Image to hack wechat -->
<!-- <img src="https://wuhaolin.cn/img/icon_wechat.png" width="0" height="0"> -->
<!-- <img src="{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}" width="0" height="0"> -->

<!-- Post Header -->
<style type="text/css">
    header.intro-header{
        background-image: url('/img/bg.jpg')
    }
</style>
<header class="intro-header" >
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <div class="post-heading">
                    <div class="tags">
                        
                          <a class="tag" href="/tags/#音视频" title="音视频">音视频</a>
                        
                    </div>
                    <h1>流媒体加密</h1>
                    <h2 class="subheading"></h2>
                    <span class="meta">
                        Posted by 吴浩麟 on
                        2017-08-01
                    </span>
                </div>
            </div>
        </div>
    </div>
</header>

<!-- Post Content -->
<article>
    <div class="container">
        <div class="row">
    <!-- Post Container -->
            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                post-container">

                <blockquote>
<p>本文只讨论应用于浏览器环境的流媒体协议的加密</p>
</blockquote>
<h2 id="为什么要加密视频"><a href="#为什么要加密视频" class="headerlink" title="为什么要加密视频"></a>为什么要加密视频</h2><p>付费观看视频的模式是很多平台的核心业务，如果视频被录制并非法传播，付费业务将受到严重威胁。因此对视频服务进行加密的技术变得尤为重要。</p>
<p>本文所指的视频加密是为了让要保护的视频不能轻易被下载，即使下载到了也是加密后的内容，其它人解开加密后的内容需要付出非常大的代价。</p>
<p>无法做到严格的让要保护的视频不被录制，原因在于你需要在客户端播放出视频的原内容，解密的流程在客户端的话不法分子就能模拟整个流程，最保守也能用屏幕录制软件录制到视频的原内容(可以通过加水印的方法缓解下)。我们的目标是让他获取原内容的代价更大。</p>
<h2 id="常见视频加密技术"><a href="#常见视频加密技术" class="headerlink" title="常见视频加密技术"></a>常见视频加密技术</h2><p>视频加密技术分为两种：</p>
<ol>
<li>防盗链：通过验证的用户才能访问到没有加密的视频内容，这种方案存在视频很容易就被下载的风险，严格来说这不属于加密。这种方式其实是资源访问授权，它实现起来简单。</li>
<li>加密视频本身：通过对称加密算法加密视频内容本身，用户获得加密后的视频内容，通过验证的用户可以获取解密视频的密钥，在客户端解密后播放。这种方式实现起来流程复杂会带来更多的计算量。</li>
</ol>
<p>一般结合这两种技术一起用，第1种技术很成熟也有很多教程就不再复述，本文主要介绍第2种加密技术。</p>
<h2 id="流媒体加密技术原理"><a href="#流媒体加密技术原理" class="headerlink" title="流媒体加密技术原理"></a>流媒体加密技术原理</h2><p>看视频分为两种，看点播和看录播。<br>要看点播可以通过下载完整个视频后再看，或者通过流媒体边下边看。<br>看直播只能通过流媒体看最新的画面。</p>
<p>加密整个视频的技术很简单，把视频看成一个文件采用加密文件的技术，这种技术太多就不介绍了。<br>加密流媒体的技术很少，也很难找到学习资料，本文主要介绍流媒体加密技术。</p>
<p>常见的应用与浏览器播放的流媒体传输协议有：</p>
<ul>
<li>HLS：Apple 推出的基于 HTTP 协议的 MP4 分片传输协议，可用于点播和直播场景。每下载一个分片都需要发生一次 HTTP 请求，所以严格来说 HLS 不能称为流媒体传输协议。</li>
<li>HTTP-FLV：基于 HTTP 长连接的 FLV 分块 tag 传输协议，可用于点播和直播场景。</li>
<li>RTMP：基于 TCP 的 FLV 分块 message 传输协议，用于 Flash 客户端。</li>
</ul>
<h4 id="流媒体加密原理"><a href="#流媒体加密原理" class="headerlink" title="流媒体加密原理"></a>流媒体加密原理</h4><p>可以看出一个规律这些流媒体传输协议都必须把视频流拆分成连续的小块之后再被传送，只不过分块的大小和视频容器的格式不一样而已。</p>
<p><strong>流媒体加密技术的核心就在于对这每一小块视频分别使用对称加密算法，在服务端加密客户端解密，通过权限验证的用户才能拿到解密一小块视频的密钥。</strong></p>
<h4 id="为什么不用-HTTPS-加密"><a href="#为什么不用-HTTPS-加密" class="headerlink" title="为什么不用  HTTPS 加密"></a>为什么不用  HTTPS 加密</h4><p>可能有人会问为什么不用 HTTPS 加密？原因是 HTTPS 在网络传输层进行非对称加密，目的是为了防止中间人窃听劫持，任何人都可以和我们的服务器建立 HTTPS 链接获取到原数据。而视频加密的目的不是为了防止有中间人窃听我们的视频数据，而是要让视频数据本身被加密。</p>
<h4 id="为什么选择对称加密"><a href="#为什么选择对称加密" class="headerlink" title="为什么选择对称加密"></a>为什么选择对称加密</h4><p>现代成熟的加密技术分为对称加密算法和公钥密码算法(非对称加密)。之所以选择对称加密是因为流媒体要求很强的实时性，数据量又很大。公钥密码算法的计算都比较复杂，效率较低，适合对少量数据进行加密。对称加密效率相对较高，所以流媒体加密首选对称加密。例如在 SSH 登入的时候会先通过公钥密码算法传输一个密钥，再用这个密钥用作对称加密算法的密钥，在数据传输过程中使用对称加密算法来提示数据传输效率。</p>
<h2 id="HLS-加密"><a href="#HLS-加密" class="headerlink" title="HLS 加密"></a>HLS 加密</h2><p>HLS 是目前最成熟的支持流媒体加密的能应用在浏览器里的流媒体传输协议，HLS 原生支持加密，下面来详细介绍它。</p>
<p>在介绍如何加密 HLS 先了解下 HLS 相比于其它流媒体传输协议的优缺点。<br>优点在于：</p>
<ul>
<li>建立在 HTTP 之上，使用简单，接入代价小。</li>
<li>分片技术有利于 CDN 加速技术的实施。</li>
<li>部分浏览器原生支持，支持点播和录播。</li>
</ul>
<p>缺点在于：</p>
<ul>
<li>用作直播时延迟太大。</li>
<li>移动端支持还好，PC端只有 Safari 原生支持。</li>
</ul>
<h4 id="HLS-加密原理"><a href="#HLS-加密原理" class="headerlink" title="HLS 加密原理"></a>HLS 加密原理</h4><p>HLS 由两部分构成，一个是 .m3u8 文件，一个是 .ts 视频文件（TS 是视频文件格式的一种）。整个过程是，浏览器会首先去请求 .m3u8 的索引文件，然后解析 m3u8，找出对应的 .ts 文件链接，并开始下载。<br><img src="https://user-images.githubusercontent.com/5773264/28862374-6f2bcb24-7797-11e7-9671-4a7a471dfb8a.png" alt="hls"></p>
<p>m3u8 文件是一个文本文件，在开启 HLS 加密时，内容大致如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#EXTM3U</span><br><span class="line">#EXT-X-VERSION:6</span><br><span class="line">#EXT-X-TARGETDURATION:10</span><br><span class="line">#EXT-X-MEDIA-SEQUENCE:26</span><br><span class="line">#EXT-X-KEY:METHOD=AES-128,URI=&quot;https://priv.example.com/key.do?k=1&quot;</span><br><span class="line">#EXTINF:9.901,</span><br><span class="line">http://media.example.com/segment26.ts</span><br><span class="line">#EXT-X-KEY:METHOD=AES-128,URI=&quot;https://priv.example.com/key.do?k=2&quot;</span><br><span class="line">#EXTINF:9.501,</span><br><span class="line">http://media.example.com/segment28.ts</span><br></pre></td></tr></table></figure></p>
<p>这个文件描述了每个 TS 分片的 URL ，但这些分片都是加密后的内容，要还原出原内容需要从<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#EXT-X-KEY:METHOD=AES-128,URI=&quot;https://priv.example.com/key.do?k=1&quot;</span><br></pre></td></tr></table></figure></p>
<p>中解析出获取解密密钥的URL <code>https://priv.example.com/key.do</code> 和对称加密算法 <code>AES-128</code> 。<br>获取到密钥后再在客户端解密出原内容。<br>可以看出启用 HLS 加密后会多出更多的事情：</p>
<ul>
<li>针对每个 TS 需要去请求获取密钥。</li>
<li>需要多提供一个给客户端获取密钥的鉴权服务。</li>
<li>针对每个 TS 需要去执行对称加密的解密计算。</li>
</ul>
<p>这会带来更多的网络请求和计算量，可能会对延迟和性能造成一定的不良影响。</p>
<h4 id="HLS-加密实战"><a href="#HLS-加密实战" class="headerlink" title="HLS 加密实战"></a>HLS 加密实战</h4><p>支持 HLS 的客户端都原生支持加密，所以要开启 HLS 加密你只需要修改你的服务端：</p>
<ul>
<li>修改 HLS 视频服务，对 TS 加密，往 m3u8 中加入 <code>EXT-X-KEY</code> 字段。</li>
<li>提供给客户端获取密钥的鉴权服务，即上面的 <code>https://priv.example.com/key.do?k=1</code> 所指向的服务。</li>
</ul>
<p>目前大多数云服务都支持 HLS 加密服务，如果你想直接搭建 HLS 加密服务可以使用 <a href="https://github.com/arut/nginx-rtmp-module/wiki/Directives#hls_keys" target="_blank" rel="noopener">nginx-rtmp-module</a>。</p>
<h4 id="HLS-兼容方案"><a href="#HLS-兼容方案" class="headerlink" title="HLS 兼容方案"></a>HLS 兼容方案</h4><p>目前 HLS 存在兼容性问题：</p>
<ul>
<li>针对桌面端低版本浏览器例如 IE，可以使用 Flash 技术来播放 HLS ，详细使用见开源项目 <a href="https://github.com/mangui/flashls" target="_blank" rel="noopener">flashls</a>。</li>
<li>针对非Safari高版本浏览器例如 Chrome ，可以使用 Media Source Extensions API 去实现播放  HLS，详细使用见开源项目 <a href="https://github.com/video-dev/hls.js" target="_blank" rel="noopener">hls.js</a>。</li>
<li>针对即不原生支持 HLS又不支持 Media Source Extensions 也不支持 Flash 的浏览器，常见于部分低版本移动端浏览器，我也找不到什么好办法。</li>
</ul>
<p>flashls 和 hls.js 都支持 HLS 加密技术。</p>
<h4 id="破解-HLS-加密"><a href="#破解-HLS-加密" class="headerlink" title="破解 HLS 加密"></a>破解 HLS 加密</h4><p>有加密就有破解，在明白 HLS 加密原理后，你想过如何去破解它吗？先定义下破解成功是指：获取到视频加密前的完整原文件。我想到的方法是：</p>
<ol>
<li>先付费买一个可正常观看受保护视频的账号。</li>
<li>用抓包工具抓下所有网络请求(可以筛选下限制到只保存 HLS 和 获取key 的请求，防止保存太多垃圾数据)。</li>
<li>第2步保存下来了加密后的 TS 分片和加密分片所需要的密钥。</li>
<li>写一个脚本以 m3u8 为索引一一解密出加密后的 TS 分片的原文件，再把 TS 拼接成完整的视频原文件。</li>
</ol>
<p>似乎破解的难度也不会很复杂。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>目前流媒体加密技术还不成熟，除了 HLS 协议提供了方便成熟的方案外，其它协议的加密技术还不成熟。</p>
<p>RTMP 协议提供了一个变种版 RTMPE 可以加密流媒体，原理和 HLS 加密类似，但是我还找不到合适的服务端去支持 RTMPE 协议。</p>
<p><a href="http://wuhaolin.cn/2017/08/01/%E6%B5%81%E5%AA%92%E4%BD%93%E5%8A%A0%E5%AF%86/">阅读原文</a></p>


                <hr>

                 <strong><a href="https://github.com/gwuhaolin/blog/issues/10" target="_blank" rel="noopener">在Github上参与本文讨论</a></strong>

                <ul class="pager">
                    
                        <li class="previous">
                            <a href="/2017/11/02/调试利器-SSH隧道/" data-toggle="tooltip" data-placement="top" title="调试利器-SSH隧道">&larr; Previous Post</a>
                        </li>
                    
                    
                        <li class="next">
                            <a href="/2017/06/27/使用HeadlessChrome做单页应用SEO/" data-toggle="tooltip" data-placement="top" title="使用HeadlessChrome做单页应用SEO">Next Post &rarr;</a>
                        </li>
                    
                </ul>
            </div>
    <!-- Side Catalog Container -->
        

    <!-- Sidebar Container -->

            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                sidebar-container">

                <!-- Featured Tags -->
                
                <section>
                    <!-- no hr -->
                    <h5><a href="/tags/">FEATURED TAGS</a></h5>
                    <div class="tags">
                       
                          <a class="tag" href="/tags/#音视频" title="音视频">音视频</a>
                        
                    </div>
                </section>
                

                <!-- Friends Blog -->
                
            </div>

        </div>
    </div>
</article>


<!-- async load function -->
<script>
    function async(u, c) {
      var d = document, t = 'script',
          o = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      o.src = u;
      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
      s.parentNode.insertBefore(o, s);
    }
</script>
<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->
<script>
    async("https://cdn.bootcss.com/anchor-js/1.1.1/anchor.min.js",function(){
        anchors.options = {
          visible: 'always',
          placement: 'right',
          icon: '#'
        };
        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');
    })
</script>
<style>
    /* place left on bigger screen */
    @media all and (min-width: 800px) {
        .anchorjs-link{
            position: absolute;
            left: -0.75em;
            font-size: 1.1em;
            margin-top : -0.1em;
        }
    }
</style>



<!-- Footer -->
<!-- Footer -->
<footer>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <ul class="list-inline text-center">
                    
                    
                    
                    <li>
                        <a target="_blank" href="https://www.zhihu.com/people/wu-hao-lin-67-15">
                            <span class="fa-stack fa-lg">
                                <i class="fa fa-circle fa-stack-2x"></i>
                                <i class="fa  fa-stack-1x fa-inverse">知</i>
                            </span>
                        </a>
                    </li>
                    

                    

                    

                    
                    <li>
                        <a target="_blank" href="https://github.com/gwuhaolin">
                            <span class="fa-stack fa-lg">
                                <i class="fa fa-circle fa-stack-2x"></i>
                                <i class="fa fa-github fa-stack-1x fa-inverse"></i>
                            </span>
                        </a>
                    </li>
                    

                    

                </ul>
                <p class="copyright text-muted">
                    Copyright &copy; 浩麟的博客 2019
                    <br>
                    Ported by <a href="https://github.com/gwuhaolin" target="_blank" rel="noopener">gwuhaolin</a> |
                    订阅本站<iframe
                            style="margin-left: 2px; margin-bottom:-5px;"
                            frameborder="0" scrolling="0" width="91px" height="20px"
                            src="https://ghbtns.com/github-btn.html?user=gwuhaolin&repo=blog&type=star&count=true">
                    </iframe>
                </p>
            </div>
        </div>
    </div>
</footer>

<!-- jQuery -->

<script src="/js/jquery.min.js"></script>


<!-- Bootstrap Core JavaScript -->

<script src="/js/bootstrap.min.js"></script>


<!-- Custom Theme JavaScript -->

<script src="/js/hux-blog.min.js"></script>



<!-- async load function -->
<script>
    function async(u, c) {
        var d = document, t = 'script',
            o = d.createElement(t),
            s = d.getElementsByTagName(t)[0];
        o.src = u;
        if (c) {
            o.addEventListener('load', function (e) {
                c(null, e);
            }, false);
        }
        s.parentNode.insertBefore(o, s);
    }
</script>


<!-- jquery.tagcloud.js -->
<script>
    // only load tagcloud.js in tag.html
    if ($('#tag_cloud').length !== 0) {
        async("https://wuhaolin.cn/js/jquery.tagcloud.js", function () {
            $.fn.tagcloud.defaults = {
                //size: {start: 1, end: 1, unit: 'em'},
                color: {start: '#bbbbee', end: '#0085a1'},
            };
            $('#tag_cloud a').tagcloud();
        })
    }
</script>

<!--fastClick.js -->
<script>
    async("https://cdn.bootcss.com/fastclick/1.0.6/fastclick.min.js", function () {
        var $nav = document.querySelector("nav");
        if ($nav) FastClick.attach($nav);
    })
</script>


<!-- Google Analytics -->


<script>
    // dynamic User by Hux
    var _gaId = 'UA-54964850-1';
    var _gaDomain = 'wuhaolin.cn';

    // Originial
    (function (i, s, o, g, r, a, m) {
        i['GoogleAnalyticsObject'] = r;
        i[r] = i[r] || function () {
            (i[r].q = i[r].q || []).push(arguments)
        }, i[r].l = 1 * new Date();
        a = s.createElement(o),
            m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        m.parentNode.insertBefore(a, m)
    })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

    ga('create', _gaId, _gaDomain);
    ga('send', 'pageview');
</script>




<!-- Side Catalog -->


<!--Google Auto AD-->
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<script>
    (adsbygoogle = window.adsbygoogle || []).push({
        google_ad_client: "ca-pub-9697944574373240",
        enable_page_level_ads: true
    });
</script>



<!-- Image to hack wechat -->
<img src="https://wuhaolin.cn /img/icon_wechat.png" width="0" height="0"/>
<!-- Migrate from head to bottom, no longer block render and still work -->
</body>

</html>
